from django.conf import settings
from django.utils.importlib import import_module

from db_utils import DatabaseConfig, LogQueriesProxy

from sqlalchemy import MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session, Mapper

import __init__ as alchemy_app
import logging
import inspect

setup_done = False

config_list = {}
engine_list = {}
session_list = {}
metadata_list = {}
base_list = {}
bindings_list = {}
DB_MODULE_NAMES = []
all_bindings = {}

""" Defaults """
ALCHEMY_MODELS_ROOT = getattr(settings, 'ALCHEMY_MODELS_ROOT', 'models')
ALCHEMY_DATABASES = getattr(settings, 'ALCHEMY_DATABASES', {} )

""" Setting name of this big fat package """
#modname = __name__.split('.')[0]
modname = ''
modelsmodname = getattr(settings, 'ALCHEMY_MODELS_MODULE', 'sqlmodels')

PROJECT_NAME = settings.SETTINGS_MODULE.split('.')[0]

def run_setup():
    """
    Inspects databases modules according to DATABASES dict
    at settings.py and creates engines/scoped sessions/metadata's/bases
    for each module.
    """
    global setup_done
    global DB_MODULE_NAMES
    global engine_list
    global session_list
    global metadata_list
    global base_list
    
    if not setup_done:
        for db_module_name, db_config_dict in ALCHEMY_DATABASES.iteritems():
            logging.debug("Setting up alchemy-db-module: '%s'" % db_module_name)
            #db_module_name = '.'.join( (modname, modelsmodname, db_module_name) )
            DB_MODULE_NAMES.append(db_module_name)
            db_config = DatabaseConfig(db_module_name, db_config_dict)
            config_list[db_module_name] = db_config
            
            engine = db_config.get_engine()
            logging.info("Using %s" % engine)
            Session = scoped_session(
                    sessionmaker(
                        bind=engine,
                        autocommit=False,
                        autoflush=True
                    )
            )
            
            engine_list[db_module_name] = engine
            session_list[db_module_name] = Session
            metadata_list[db_module_name] = MetaData(bind=engine)
            base_list[db_module_name] = declarative_base(bind=engine)
        setup_done = True
        
""" Setting things up """
run_setup()

def _get_proper_mod_name(mod_name):
    logging.debug("Call with mod_name: '%s'" % mod_name)
    proper_mod_name = mod_name.rstrip('.').split('.')[-1]
    return proper_mod_name

def get_config(db_module_name):
    global config_list
    db_module_name = _get_proper_mod_name(db_module_name)
    return config_list[db_module_name]

def get_engine(db_module_name):
    global engine_list
    db_module_name = _get_proper_mod_name(db_module_name)
    return engine_list[db_module_name]

def get_metadata(db_module_name):
    global metadata_list
    db_module_name = _get_proper_mod_name(db_module_name)
    return metadata_list[db_module_name]

def get_Base(db_module_name):
    global base_list
    db_module_name = _get_proper_mod_name(db_module_name)
    return base_list[db_module_name]

def import_models_module(name):
    logging.debug("Importing module: '%s'" % name)
    db_config = get_config(name)
    if db_config.db_module:
        # Prevent module names clash
        if db_config.db_module.split('.')[0] == PROJECT_NAME:
            name = db_config.db_module
        else:
            name = '.'.join( [PROJECT_NAME, db_config.db_module] )
        logging.debug('module_path changed to "%s"' % name)
    mod = import_module(name)
    return mod

def get_models(db_module_name):
    """
    Returns SQLAlchemy models within db_module.
    """
    db_module = import_models_module(db_module_name)
    def is_mapped_class(obj):
        is_Mapper = isinstance(obj, Mapper)
        is_Base = hasattr(obj, '__base__') and obj.__base__ is get_Base(db_module_name)
        return is_Mapper or is_Base
    mappers = inspect.getmembers(db_module, is_mapped_class)
    models = [ mapper[1] for mapper in mappers]
    return models

def setup_bindings(db_module_name):
    """
    Sets up model modules.
    """
    global all_bindings
    global bindings_list
    global DB_MODULE_NAMES
    
    if bindings_list.has_key(db_module_name):
        return
    module_path = '.'.join( filter(bool, [ALCHEMY_MODELS_ROOT, db_module_name]) )
    #db_module = import_module(module_path)
    logging.debug("Trying to import module using path: '%s'" % module_path)
    engine = get_engine(db_module_name)
    models = get_models(db_module_name)
    bindings = {}
    for model in models:
        logging.debug("Binding '%s' with '%s'" % (model, engine))
        bindings[model] = engine
    bindings_list[db_module_name] = bindings
    all_bindings.update(bindings)

def get_bindings(db_module_name=None):
    """
    Returns bindings of db_module.
    Need every mapped class to be specified in imports at module level.
    It means that if model classes are split between many files they
    need to be imported in the __init__.py file at module level.
    """
    global all_bindings
    global bindings_list
    global DB_MODULE_NAMES
    
    if db_module_name is None:
        if not all_bindings:
            for db_module_name in DB_MODULE_NAMES:
                setup_bindings(db_module_name)
        return all_bindings
    elif bindings_list.has_key(db_module_name):
        return bindings_list[db_module_name]
    else:
        """
        Called for the first time. Will setup all bindings and
        return proper ones.
        """
        for _db_module_name in DB_MODULE_NAMES:
            setup_bindings(_db_module_name)
        return bindings_list[db_module_name]

def get_Session(db_module_name=None):
    if db_module_name is None:
        GlobalSession = scoped_session(
            sessionmaker(twophase=True, autoflush=True, autocommit=False))
        GlobalSession.configure(binds = get_bindings())
        return GlobalSession
    global session_list
    db_module_name = _get_proper_mod_name(db_module_name)
    return session_list[db_module_name]

""" Utils functions """

def create_all_databases():
    """

    """
    for db_module_name in DB_MODULE_NAMES:
        logging.info("Creating tables for %s" % db_module_name)
        get_bindings(db_module_name)
        metadata = get_metadata(db_module_name)
        engine = get_engine(db_module_name)
        Base = get_Base(db_module_name)
        
        metadata.create_all(engine)
        Base.metadata.create_all()

